#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../util.h"
#include "../errormsg.h"
#include "../symbol.h"
#include "temp.h"
#include "tree.h"
#include "printtree.h"
#include "frame.h"
#include "translate.h"

//int Tr_debugFlag = TRUE;
int Tr_debugFlag = TRUE;
//int Tr_debugEnvFlag = TRUE;
int Tr_debugEnvFlag = FALSE;

static Tr_level outermost = NULL;

Tr_access Tr_Access(Tr_level level, F_access acs)
{
	Tr_access p = checked_malloc(sizeof *p);
	p->level = level;
	p->access = acs;
	return p;
}

Tr_accessList Tr_AccessList(Tr_access head, Tr_accessList tail)
{
	Tr_accessList p = checked_malloc(sizeof *p);
	p->head = head;
	p->tail = tail;
	return p;
}

Tr_level Tr_newLevel(Tr_level parent, Temp_label name, F_accessList formals)
{
	Tr_level p = checked_malloc(sizeof (*p));
	F_accessList f_formals;
	Tr_accessList tr_formals, q = NULL;

	p->parent = parent;
	p->frame = F_newFrame(name, formals);
	f_formals = F_Formals(p->frame);
	for(; f_formals; f_formals = f_formals->tail){
		Tr_access tacs = Tr_Access(p, f_formals->head);
		if(q){
			q->tail = Tr_AccessList(tacs, NULL);
			q = q->tail;
		}
		else p->formals = q = Tr_AccessList(tacs, NULL);
	}
	p->locals = NULL;
	return p;
}

Tr_level Tr_outermost(void)
{
	if(!outermost)
		outermost = Tr_newLevel(NULL, Temp_newlabel(), NULL);
	return outermost;
}

Tr_accessList Tr_formals(Tr_level level)
{
	return level->formals;
}

Tr_access Tr_allocLocal(Tr_level level, bool escape)
{
	F_access facs = F_allocLocal(level->frame, escape);
	Tr_access tacs = Tr_Access(level, facs);

	if(level->locals){
		Tr_accessList list = level->locals;
		while(list->tail) list = list->tail;
		list->tail = Tr_AccessList(tacs, NULL);
	}
	else
		level->locals = Tr_AccessList(tacs, NULL);

  return tacs;
}


F_frame Tr_levelframe(Tr_level level)
{
	return level->frame;
}

static Tr_access Tr_staticlink(Tr_level level)
{
	// MORE_DETAIL: now abort static link
	assert(level);
	EM_debug(5, "tr get static link");
	if(level->formals)	return level->formals->head;
	return NULL;
}



/////////////////////////

static patchList PatchList(Temp_label *head, patchList tail)
{
	patchList list = checked_malloc(sizeof (*list));
	list->head = head;
	list->tail = tail;
	return list;
}

Tr_expList Tr_ExpList(Tr_exp head, Tr_expList tail)
{
	Tr_expList p = checked_malloc(sizeof(*p));
	p->head = head;
	p->tail = tail;
	return p;
}


///////////////


static Tr_exp Tr_Ex(T_exp expr)
{
	Tr_exp p = checked_malloc(sizeof (*p));
	p->kind = TR_EX;
	p->u.ex = expr;
	return p;
}

static Tr_exp Tr_Nx(T_stm stmt)
{
	Tr_exp p = checked_malloc(sizeof(*p));
	p->kind = TR_NX;
	p->u.nx = stmt;
	EM_debug(7, "leave Tr_Nx");
	return p;
}

static Tr_exp Tr_Cx(patchList trues, patchList falses, T_stm stmt)
{
	Tr_exp p = checked_malloc(sizeof(*p));
	p->kind = TR_CX;
	p->u.cx.trues = trues;
	p->u.cx.falses = falses;
	p->u.cx.stm = stmt;
	return p;
}

static void doPatch(patchList tList, Temp_label label)
{
	for(; tList; tList = tList->tail){
		*(tList->head) = label;
	}
}

static T_exp unEx(Tr_exp e)
{
	switch(e->kind){
		case TR_EX:
			return e->u.ex;
		case TR_NX:
			return T_Eseq(e->u.nx, T_Const(0));
		case TR_CX: {
			Temp_temp tmp = Temp_newtemp();
			Temp_label t = Temp_newlabel();
			Temp_label f = Temp_newlabel();
			doPatch(e->u.cx.trues, t);
			doPatch(e->u.cx.falses, f);
			return T_Eseq(T_Move(T_Temp(tmp), T_Const(1)),
					T_Eseq(e->u.cx.stm,
					 T_Eseq(T_Label(f),
					  T_Eseq(T_Move(T_Temp(tmp), T_Const(0)),
					   T_Eseq(T_Label(t),
					   		T_Temp(tmp))))));
		}
	}
	assert(0);
	return NULL;
}

static T_stm unNx(Tr_exp e)
{
	switch(e->kind){
		case TR_EX:
			return T_Exp(e->u.ex);
		case TR_NX:
			return e->u.nx;
		case TR_CX: {
			Temp_label label = Temp_newlabel();
			doPatch(e->u.cx.trues, label);
			doPatch(e->u.cx.falses, label);
			return T_Seq(e->u.cx.stm, T_Label(label));
		}
	}

	assert(0);
	return NULL;
}

static Cx unCx(Tr_exp e)
{
	Cx cx = NULL;

	switch(e->kind)
	{
		case TR_EX:
			cx->stm = T_Cjump(
				T_eq, e->u.ex, T_Const(0), NULL, NULL);
			cx->trues = PatchList(&(cx->stm->u.CJUMP.true), NULL);
			cx->falses = PatchList(&(cx->stm->u.CJUMP.false), NULL);
			return cx;
		case TR_NX:
			assert(0);
		case TR_CX:
			return (&e->u.cx);
	}

	assert(0);
	return cx;
}

Tr_exp Tr_Num(int num)
{
	return Tr_Ex(T_Const(num));
}

Tr_exp Tr_String(string str)
{
	if(Tr_debugFlag) EM_debug(4, "Tr_string : %s", String(str));
	Temp_label label = Temp_newlabel();
	F_frag frag = F_StringFrag(label, str);
	F_FragList(frag, NULL);
	if(Tr_debugFlag) EM_debug(3, "Tr_string leave");
	return Tr_Ex(T_Name(label));
}

Tr_exp Tr_Call(Tr_level level, Temp_label label, Tr_expList args)
{
	EM_debug(3, "Tr_call");
	T_exp func = T_Name(label);
	EM_debug(4, "call get fp");
	// MORE_DETAIL
	Tr_access acs = Tr_staticlink(level);
	T_exp fp;
	if(!acs) fp = T_Const(0);
	else fp = T_Const(F_getoffset(Tr_staticlink(level)->access));
	EM_debug(4, "call get args");
	T_expList l_args = T_ExpList(fp, NULL);
	for(; args; args = args->tail){
		l_args->tail = T_ExpList(unEx(args->head), NULL);
		l_args = l_args->tail;
	}
	return Tr_Ex(T_Call(func, l_args));
}

Tr_exp Tr_Op(int op, Tr_exp left, Tr_exp right)
{
	T_exp l = unEx(left);
	T_exp r = unEx(right);
	return Tr_Ex(T_Binop(op, l, r));
}

Tr_exp Tr_Rel(int op, Tr_exp left, Tr_exp right)
{
	T_stm stm = T_Cjump(op, unEx(left), unEx(right), NULL, NULL);
	return Tr_Cx(PatchList(&(stm->u.CJUMP.true), NULL),
				 PatchList(&(stm->u.CJUMP.false), NULL),
				 stm);
}

Tr_exp Tr_StringRel(int op, Tr_exp left, Tr_exp right)
{
	T_exp exp = F_externalCall("_CompareString",
					T_ExpList(left->u.ex, T_ExpList(right->u.ex, NULL)));
	T_stm stm = T_Cjump(op, exp, T_Const(0), NULL, NULL);
	return Tr_Cx(PatchList(&(stm->u.CJUMP.true), NULL),
				 PatchList(&(stm->u.CJUMP.false), NULL),
				 stm);
}

// ??
Tr_exp Tr_Record(Tr_expList fields, int size)
{
	T_exp addr = T_Temp(Temp_newtemp());
	T_exp alloc = F_externalCall(
		"_Alloc", T_ExpList(T_Const(size*WORD_SIZE), NULL));
	Tr_expList p = fields;
	T_stmList q = NULL, r = NULL;
	int i;

	for(i = 0; p; p = p->tail, i++){
		Tr_exp field = p->head;
		T_exp offset = T_Binop(T_plus, addr,
							T_Const(i*WORD_SIZE));
		T_stm stm = T_Move(T_Mem(offset), unEx(field));
		T_stmList list = T_StmList(stm, NULL);

		if(q){
			r->tail = list;
			r = list;
		}
		else
			q = r = list;

	}
	return Tr_Ex(
		T_Eseq(
			T_Seq(T_Move(addr, alloc), q->head),
			addr));
}

Tr_exp Tr_Array(Tr_exp size, Tr_exp init)
{
	return Tr_Ex(F_externalCall(
		"_InitArray", T_ExpList(unEx(size), T_ExpList(unEx(init), NULL))));
}

Tr_exp Tr_Seq(T_stmList stms)
{
	T_stmList res = NULL, next = NULL;
	T_stmList p = stms;
	Tr_exp texpr = checked_malloc(sizeof(*texpr));
	texpr->kind = TR_NX;

	for(; p; p = p->tail){
		texpr->u.nx = p->head;
		T_stm stmtt = unNx(texpr);
		if(res)
			next = next->tail = T_StmList(stmtt, NULL);
		else
			res = next = T_StmList(stmtt, NULL);
	}

	T_stmList resp = res;
	int stmcnt = 0, i;

	while(resp) resp = resp->tail, stmcnt++;
	//
	T_stm stmres = NULL;
	if(stmcnt < 2) {
		if(Tr_debugFlag) EM_debug(3, "stms less than 2");
		stmres = res->head;
	}
	else{
		T_stm* reslist = NULL;
		reslist = checked_malloc(sizeof(T_stm)*stmcnt);
		resp = res;

		for(i = 0; i < stmcnt; i++){
			reslist[i] = resp->head;
			resp = resp->tail;
		}

		i = stmcnt - 1;
		stmres = T_Seq(reslist[i-1], reslist[i]);
		--i;
		while(i > 0){
			stmres = T_Seq(reslist[--i], stmres);
		}
	}
	EM_debug(2, "leave tr_seq");
	return Tr_Nx(stmres);
}

Tr_exp Tr_If(Tr_exp cond, Tr_exp thenc, Tr_exp elsec)
{
	Temp_label t = Temp_newlabel();
	Temp_label f = Temp_newlabel();
	Temp_label done = Temp_newlabel();
	Cx cx = unCx(cond);
	T_exp res = T_Temp(Temp_newtemp());

	doPatch(cx->trues, t);
	doPatch(cx->falses, f);
	if(elsec){
		return Tr_Ex(T_Eseq(T_Seq(cx->stm,
							 T_Seq(T_Label(t),
							  T_Seq(T_Move(T_Mem(res), unEx(thenc)),
							   T_Seq(T_Jump(T_Name(done), Temp_LabelList(done, NULL)),
							   	T_Seq(T_Label(f),
							   	 T_Seq(T_Move(T_Mem(res), unEx(elsec)),
							   	 	T_Label(done))))))), res));
	}
	else{
		return Tr_Nx(T_Seq(cx->stm,
					  T_Seq(T_Label(t),
					   T_Seq(unNx(thenc),
					   	T_Label(f)))));
	}
}

Tr_exp Tr_While(Tr_exp cond, Tr_exp body)
{
	Temp_label start = Temp_newlabel();
	Temp_label loop = Temp_newlabel();
	Temp_label done = Temp_newlabel();
	Cx cx = unCx(cond);

	doPatch(cx->trues, loop);
	doPatch(cx->falses, done);

	return Tr_Nx(T_Seq(cx->stm,
				  T_Seq(T_Label(loop),
				   T_Seq(unNx(body),
				   	T_Seq(T_Jump(T_Name(start), Temp_LabelList(start, NULL)),
				   		T_Label(done))))));
}

Tr_exp Tr_For(Tr_access access, Tr_exp low, Tr_exp high, Tr_exp body)
{
	T_exp var = F_Exp(access->access, T_Temp(F_FP()));
	Temp_label start = Temp_newlabel();
	Temp_label loop = Temp_newlabel();
	Temp_label done = Temp_newlabel();
	T_stm cond = T_Cjump(T_le, var, unEx(high), loop, done);

	return Tr_Nx(T_Seq(T_Label(start),
						T_Seq(cond,
						 T_Seq(T_Label(loop),
						  T_Seq(unNx(body),
						  	T_Seq(T_Move(var, T_Binop(T_plus, var, T_Const(1))),
						  		T_Label(done)))))));
}

Tr_exp Tr_Assign(Tr_exp left, Tr_exp rexp)
{
	return Tr_Nx(T_Move(unEx(left), unEx(rexp)));
}

Tr_exp Tr_SimpleVar(Tr_access access, Tr_level level)
{
	T_exp fp = T_Temp(F_FP());
	if(Tr_debugFlag) EM_debug(2, "in Tr_SimpleVar");
	return Tr_Ex(F_Exp(access->access, fp));
}

Tr_exp Tr_FieldVar(Tr_exp record, int index)
{
	return Tr_Ex(T_Mem(T_Binop(T_plus,
						unEx(record),
						T_Binop(T_mul,
							T_Const(index),
							T_Const(WORD_SIZE))
						)));
}

void Tr_printExp(Tr_exp exp)
{
	printStmList(stdout, T_StmList(unNx(exp), NULL));
}
